package nancal.mp.mvc.ops

import cn.hutool.core.text.AntPathMatcher
import io.kubernetes.client.openapi.ApiClient
import nancal.mp.db.mongo.JenkinsLanguageEnum
import nancal.mp.db.mongo.entity.dev.AffinitySetting
import nancal.mp.db.mongo.extend.getAppDeploySetting
import nancal.mp.db.mongo.mor
import nancal.mp.imageNameVersion
import nancal.mp.model.K8sAppServiceStatusInfo
import nancal.mp.model.K8sDeployServiceModel
import nancal.mp.model.replaceVarModel
import nancal.mp.service.*
import nancal.mp.util.tidyK8sYaml
import nbcp.base.annotation.DefaultNumberValue
import nbcp.base.annotation.mvc.AdminSysOpsAction
import nbcp.base.comm.*
import nbcp.base.extend.*
import nbcp.base.utils.RecursionUtil
import nbcp.myoql.db.mongo.query
import nbcp.myoql.db.mongo.queryById
import nbcp.myoql.db.mongo.update
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.web.bind.annotation.*
import java.time.LocalDateTime
import javax.servlet.http.HttpServletResponse

@RequestMapping("/ops/k8s/deployment")
@RestController
@AdminSysOpsAction
class K8sDeploymentController {

    @Autowired
    lateinit var k8sDeployment: K8sDeploymentService;

    @Autowired
    lateinit var k8sService: K8sService;


    @RequestMapping("/list", method = arrayOf(RequestMethod.POST, RequestMethod.GET))
    fun list(
        targetEnv: String,
        namespace: String,
        name: String,
        productLineCode: String,
        isReady: Boolean?,
        language: JenkinsLanguageEnum?,
        versionMin: String,
        versionMax: String,
        skip: Int,
        @DefaultNumberValue(99) take: Int
    ): ListResult<K8sAppServiceStatusInfo> {
        var namespace = namespace;

        if (namespace.isNullOrEmpty()) {
            namespace = mor.dev.productLine.queryByCode(productLineCode).toEntity()?.clusterCode
                ?: "";
        }

        if (namespace.isNullOrEmpty()) {
            return ListResult.error("找不到产品线信息");
        }
        return k8sDeployment.list(
            targetEnv,
            namespace,
            name,
            productLineCode,
            isReady,
            language,
            versionMin,
            versionMax,
            skip,
            take
        );
    }

    @PostMapping("/yaml/chang-app", consumes = arrayOf("text/plain"), produces = arrayOf("text/plain"))
    fun changeApp(@RequestBody content: String, imageName: String): String {
        var list = content.FromListYarmTextWithSplit(JsonMap::class.java)

        var oriDockerName = list.firstOrNull { it.getValueByWbsPath("metadata.name") != null }?.get("name").AsString()
        if (oriDockerName.isNullOrEmpty()) {
            throw RuntimeException("找不到 metadata.name!");
        }

        var dockerName = imageName.split(":").first()

        return list.map { item ->
            RecursionUtil.recursionJson(item, { map ->
                var mmap = map as MutableMap<String, Any?>
                for (key in map.keys.toTypedArray()) {

                    if (map[key] == oriDockerName) {
                        map.put(key, dockerName)
                    }
                }
                return@recursionJson true;
            })

            item.tidyK8sYaml()
                .ToYaml()
        }
            .joinToString("\n")
    }

    @PostMapping("/yaml/tidy", consumes = arrayOf("text/plain"), produces = arrayOf("text/plain"))
    fun tidyYam(@RequestBody content: String): String {
        return content.FromListYarmTextWithSplit(JsonMap::class.java)
            .map { it.tidyK8sYaml(true).ToYaml() }
            .joinToString("\n")
    }

    @GetMapping("/status")
    fun status(
        targetEnv: String,
        namespace: String,
        name: String
    ): ApiResult<K8sAppServiceStatusInfo> {
        return ApiResult.of(k8sDeployment.getCurrentReplicaSetStatus(targetEnv, namespace, name))
    }


    @RequestMapping("/get-deployment-yaml", method = arrayOf(RequestMethod.POST, RequestMethod.GET))
    fun getDeploymentYaml(targetEnv: String, name: String, namespace: String): ApiResult<String> {
        k8sDeployment.getDeploymentYaml(targetEnv, namespace, name)
            .apply {
                if (this.isEmpty()) {
                    return ApiResult.error("找不到服务${name}")
                }
                return ApiResult.of(this.ToYaml())
            }
    }


    @PostMapping("/apply-deployment-yaml")
    fun applyDeploymentYaml(
        targetEnv: String,
        @RequestBody yaml: String,
        namespace: String,
        name: String,
        envProfile: String,   // 仅是 dev,test,main
    ): JsonResult {
        return k8sDeployment.applyDeploymentYaml(
            targetEnv,
            namespace,
            name,
            yaml,
            envProfile
        );
    }

    //    data class DevOpsK8sMemData(
//        var language: JenkinsLanguageEnum = JenkinsLanguageEnum.Java,
//        var limitMem: String = ""
//    )
    @Autowired
    lateinit var configMapService: K8sConfigMapService

    @Autowired
    lateinit var istioService: IstioService


    /**
     * 设置生产服务器的版本, 测试服务器在 shell 中调用！
     */
    @PostMapping("/sync-prod-version")
    fun syncDeplymentVersion(
        namespace: String,
        language: JenkinsLanguageEnum,
        deployment: String,
        version: String
    ): JsonResult {
        var syncSetting = mor.sys.k8sSyncSetting.query()
            .where { it.isActive mongoEquals true }
            .toEntity()!!;

        var prodK8s = ApiClient();
        prodK8s.basePath = syncSetting.prodK8s.apiServerHost
        prodK8s.setApiKey(syncSetting.prodK8s.apiToken);
        prodK8s.setApiKeyPrefix("Bearer")
        prodK8s.isVerifyingSsl = false;

//        var testDeployment = k8sDeployment.getDeploymentInfo("", namespace, deployment);
//        if (testDeployment.data == null) {
//            return JsonResult.error("找不到 ${deployment}")
//        }

        var imageNameVersion = language.name.lowercase() + "/" + deployment + ":" + version
        var newImage = syncSetting.prodRegistry.host + "/" + imageNameVersion

        k8sDeployment.setImageName(prodK8s, namespace, deployment, newImage);

        return JsonResult();
    }

    /**
     * 🚩 核心接口：部署！
     */
    @RequestMapping(
        "/deploy-with-log/{jenkinsLogId}",
        method = arrayOf(RequestMethod.POST, RequestMethod.GET)
    )
    fun deployWithLog(jenkinsLogId: String): JsonResult {
        var jenkinsLog = mor.dev.jenkinsLog.queryById(jenkinsLogId)
            .toEntity()
            .must()
            .elseThrow { "找不到JenkinsLogId" }


        var def = K8sDeployServiceModel.load(jenkinsLog);


        if (k8sDeployment.existsDeployment(
                jenkinsLog.buildInfo.targetEnv,
                def.namespace,
                def.serviceName
            )
        ) {
            //仅更新镜像

            //@imageTagPrefix@@serviceName@:@imageTagVersion@

            return k8sDeployment.setImageName(
                jenkinsLog.buildInfo.targetEnv,
                def.namespace,
                def.serviceName,
                def.fullImageVersion
            );
        }


        var deploymentYaml = "";

        if (jenkinsLog.currentGitFolder.config.k8sDeployment.id.HasValue) {
            deploymentYaml =
                mor.dev.userK8sDeployment.queryById(jenkinsLog.currentGitFolder.config.k8sDeployment.id)
                    .toEntity()!!.yaml
        } else {
            deploymentYaml = def.language.getK8sDeploymentYamlContent(def.languageVersion, def.targetEnv);
        }


        if (deploymentYaml.isEmpty()) {
            throw RuntimeException("找不到 k8s-deployment.yaml")
        }


//        deploymentYaml = deploymentYaml
//            .replace("@k8sDeploymentMaxMemory@", mem)
//            .replace("@k8sDeploymentMaxMemory/2@", getMemHarf(mem))


        mor.dev.jenkinsLog.update()
            .where { it.gitData.language mongoEquals def.language }
            .where { it.gitData.productLine.code mongoEquals def.productLineCode }
            .where { it.buildInfo.serviceName mongoEquals def.serviceName }
            .apply {
                if (def.msDeployEnvEnum.AsString().HasValue) {
                    this.where { it.buildInfo.msDeployEnvEnum mongoEquals def.msDeployEnvEnum.AsString() }
                } else {
                    this.where { it.buildInfo.msDeployEnvEnum.mongoIsNullOrEmpty() }
                }
            }
            .exec()


        var appSetting =
            mor.dev.appDeploySetting.getAppDeploySetting(jenkinsLog.buildInfo.targetEnv);


        //Yaml变量化，参数替换
        deploymentYaml = deploymentYaml.replaceVarModel(def)

        var languageSetting = appSetting.languageSettings.first { it.language == def.language };

        //处理亲和性
        deploymentYaml = procK8sAffinity(languageSetting.affinitySettings, def.serviceName, deploymentYaml);


        k8sDeployment.applyDeploymentYaml(
            jenkinsLog.buildInfo.targetEnv,
            def.namespace,
            def.serviceName,
            deploymentYaml
        ).apply {
            if (this.msg.HasValue) {
                return this;
            }
        }

        var serviceYaml = def.language.getK8sServiceYamlContent(def.targetEnv);

        k8sService.applyServiceYaml(
            jenkinsLog.buildInfo.targetEnv,
            def.namespace,
            def.serviceName,
            serviceYaml.replaceVarModel(def)
        ).apply {
            if (this.msg.HasValue) {
                return this;
            }
        }


        //如果启用 istio
        if (appSetting.k8sConfig.enableIstio) {
            istioService.applyVirtualServiceYaml(
                jenkinsLog.buildInfo.targetEnv,
                def.namespace,
                def.serviceName,
                appSetting.k8sConfig.devUserIfIstio
            );

            istioService.applyDestinationRuleYaml(
                jenkinsLog.buildInfo.targetEnv,
                def.namespace,
                def.serviceName
            );
        }


        return JsonResult()
    }


    private fun procK8sAffinity(
        affinitySettings: MutableList<AffinitySetting>,
        serviceName: String,
        deploymentYaml: String
    ): String {

        var matcher = AntPathMatcher("-")
        var setting = affinitySettings.firstOrNull {
            var ret = matcher.match(it.antPathMatcher, serviceName)
            if (ret) {
                return@firstOrNull ret;
            } else {
                return@firstOrNull !!ret
            }
        };

        if (setting == null) {
            return deploymentYaml;
        }

        /*
spec:
  template:
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: node-role
                operator: In
                values:
                - job
         */

        var affinityYaml = """
affinity:
  nodeAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
      nodeSelectorTerms:
      - matchExpressions:
        - key: node-role
          operator: ${if (setting.inOrNot) "In" else "NotIn"}
          values:
          - ${setting.nodeRole}
""".FromYamlText<JsonMap>()
        var json = deploymentYaml.FromYamlText<JsonMap>();
        var spec = json.getValueByWbsPath("spec.template.spec") as MutableMap<String, Any>

        spec.put("affinity", affinityYaml.get("affinity")!!);

        return json.ToYaml()
    }

//    @GetMapping("/deploy-test/{jenkinsLogId}")
//    fun deployToTest(jenkinsLogId: String): JsonResult {
//        var jenkinsLog = mor.dev.jenkinsLog.queryById(jenkinsLogId).toEntity().must().elseThrow { "找不到JenkinsLogId" }
//        var def = K8sService.ServiceModel();
//        var productLine = mor.dev.productLine.queryById(jenkinsLog.gitData.productLine.id).toEntity().must().elseThrow { "找不到 productLine" }
//
//        def.language = jenkinsLog.gitData.language;
//        def.dockerName = jenkinsLog.gitBuildInfo.jobName;
//        def.namespace = productLine.k8s_ns;
//        def.profile = jenkinsLog.gitBuildInfo.envName
//        def.dockerImage = jenkinsLog.gitBuildInfo.dockerImage
//        def.productLineCode = jenkinsLog.gitData.productLine.code
//        def.vpath = "/" + jenkinsLog.gitBuildInfo.appName.replace("/", "")
//        return deploy(def);
//    }


    /**
     * 获取日志
     */
    @GetMapping("/status/{jenkinsLogId}")
    fun getStatus(
        jenkinsLogId: String,
        response: HttpServletResponse
    ): ApiResult<K8sAppServiceStatusInfo> {
        var jenkinsLog = mor.dev.jenkinsLog.queryById(jenkinsLogId).toEntity().must()
            .elseThrow { "找不到 JenkinsLogId:" + jenkinsLogId }
        var productLine = mor.dev.productLine.queryById(jenkinsLog.productLine.id).toEntity().must()
            .elseThrow { "找不到 productLine: " + jenkinsLog.productLine.id }

        var ret = k8sDeployment.getCurrentReplicaSetStatus(
            jenkinsLog.buildInfo.targetEnv,
            productLine.clusterCode,
            jenkinsLog.buildInfo.serviceName
        )

        var imageVersion = ret.imageNameVersion.split(":").last()
        if (imageVersion != jenkinsLog.buildVersion.dockerImageVersion) {
            response.status = 500
        }

        return ApiResult.of(ret)
    }

    @PostMapping("/delete/{namespace}/{name}", "/delete/{name}")
    fun delete(targetEnv: String, namespace: String, name: String): JsonResult {
        return k8sDeployment.delete(targetEnv, namespace, name)
    }

    @RequestMapping("/restart-nginx", method = arrayOf(RequestMethod.POST, RequestMethod.GET))
    fun restartNginx(targetEnv: String, namespace: String) {
        var k8s_ns = namespace.AsString(config.appGroup.AsString("default"))
        k8sDeployment.setEnv(targetEnv, k8s_ns, "nginx", 0, LocalDateTime.now().AsString())
    }


//    @PostMapping("/set-k8s-memory")
//    fun setK8sMemory(
//        targetEnv: String,
//        namespace: String,
//        language: JenkinsLanguageEnum,
//        max: Int
//    ) {
//        k8sDeployment.list(targetEnv, namespace, language = language).data
//            .forEach {
//                k8sDeployment.setMemory(
//                    targetEnv,
//                    namespace,
//                    it.name,
//                    max
//                );
//            }
//    }

//    @PostMapping("/set-k8s-image")
//    fun setK8sImage(
//        targetEnv: String,
//        namespace: String,
//        name: String,
//        imageName: String
//    ) {
//        k8sDeployment.setImageName(
//            targetEnv,
//            namespace,
//            name,
//            imageName
//        );
//    }
}
